/****************************************************************************
 * Copyright 2009 Huascar A. Sanchez                                        *
 *                                                                          *
 * Licensed under the Apache License, Version 2.0 (the "License");          *
 * you may not use this file except in compliance with the License.         *
 * You may obtain a copy of the License at                                  *
 *                                                                          *
 *     http://www.apache.org/licenses/LICENSE-2.0                           *
 *                                                                          *
 * Unless required by applicable law or agreed to in writing, software      *
 * distributed under the License is distributed on an "AS IS" BASIS,        *
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
 * See the License for the specific language governing permissions and      *
 * limitations under the License.                                           *
 ****************************************************************************/
package com.gotobject.filter;

import com.gotobject.filter.predicate.AbstractPredicate;
import com.gotobject.filter.predicate.Predicate;
import com.gotobject.filter.spi.Function;
import org.junit.Test;

import java.util.Arrays;
import java.util.List;

import static com.gotobject.filter.FilterQ.from;
import static com.gotobject.filter.common.Aggregations.max;
import static com.gotobject.filter.common.Aggregations.min;
import static com.gotobject.filter.common.Predicates.between;
import static com.gotobject.filter.common.Predicates.compose;
import static com.gotobject.filter.common.Predicates.conj;
import static com.gotobject.filter.common.Predicates.disj;
import static com.gotobject.filter.common.Predicates.eq;
import static com.gotobject.filter.common.Predicates.gt;
import static com.gotobject.filter.common.Predicates.lt;
import static com.gotobject.filter.common.Predicates.not;
import static com.gotobject.filter.common.Predicates.startsWith;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;

/**
 * @author huascar.sanchez@gmail.com (Huascar A. Sanchez)
 */
public class EvaluatePredicateTest {
    @Test
    public void restrictionOperator_Where_Equality() throws Exception {
        final Person a = new Person("John", "Peterson");
        final Person b = new Person("Pete", "Peterzon");
        final List<Person> ppl = Arrays.asList(a, b);

        for(Person each : from(ppl).where(eq("lastName", "Peterson")).select()){
           assertEquals("any person with the lastname of Peterson", "Peterson", each.getLastName());
        }

        for(Person each : from(ppl).where(eq(a)).select()){
            assertEquals("a should be the filtered person.", a, each);
        }
    }


    @Test(expected = FilteringException.class)
    public void partitionOperator_Where_Failed() throws Exception {
        final Predicate<Integer> a = new AbstractPredicate<Integer>(){
            @Override
            public boolean apply(Integer thing) {
                return false;
            }

            @Override
            public String toString() {
                return "a";
            }
        };
        from(Arrays.asList(1, 3, 67, 45, 44, 88, 60, 12, 8)).select(a);
    }


    @Test(expected = FilteringException.class)
    public void partitionOperator_Where_Failed_InvalidCondition() throws Exception {
        final Predicate<Integer> a = new AbstractPredicate<Integer>(){
            @Override
            public boolean apply(Integer thing) {
                return false;
            }

            @Override
            public String toString() {
                return "a";
            }
        };
        from(Arrays.asList(1, 3, 67, 45, 44, 88, 60, 12, 8)).where(a, a).select();
    }    


    @Test(expected = FilteringException.class)
    public void partitionOperator_Where_Failed_DueTo_NULL() throws Exception {
        from(Arrays.asList(1, 3, 67, 45, 44, 88, 60, 12, 8)).where(null, null).select();
    }

    @Test
    public void restrictionOperator_Where_Series() throws Exception {
        final List<Integer> someNumbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        for(Integer each : from(someNumbers).where(gt(1).and(lt(9)), not(eq(5))).select()){
            assertTrue(
                    "should be either 2, 3, 4, 5, 6, 7, 8",
                    each.equals(2) || each.equals(3) || each.equals(4) ||
                    each.equals(6) || each.equals(7) || each.equals(8)
            );
        }
    }


    @Test
    public void restrictionOperator_Where_Negated_Equality() throws Exception {
        final Person a = new Person("John", "Peterson");
        final Person b = new Person("Pete", "Peterzon");
        final List<Person> ppl = Arrays.asList(a, b);

        for(Person each : from(ppl).where(not(eq(a))).select()){
            assertEquals("b should be the filtered person.", b, each);
        }
    }

    @Test
    public void retrictionOperator_Where_StartWith() throws Exception {
        final Person a = new Person("John", "Peterson");
        final Person b = new Person("Pete", "Peterz");
        final Person c = new Person("Mario", "Anders");
        final Person d = new Person("Mary", "Medieta");

        final List<Person> ppl = Arrays.asList(a, b, c, d);
        for(Person each : from(ppl).where(startsWith("a", "on")).select()){
            assertTrue("a & b", each.equals(a) || each.equals(d));
        }
    }

    @Test
    public void restrictionOperator_Where_Range() throws Exception {   
        final List<Integer> someNumbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        for(Integer each : from(someNumbers).where(between(2, 8)).select()){
            assertTrue(
                    "should be either 2, 3, 4, 5, 6, 7, 8",
                    each.equals(2) || each.equals(3) || each.equals(4) ||
                    each.equals(5) || each.equals(6) || each.equals(7) ||
                    each.equals(8)
            );
        }
    }

    @Test
    public void restrictionOperator_Where_Range_CompoundConditions() throws Exception {
        final List<Integer> someNumbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        for(Integer each : from(someNumbers).where(gt(1).and(lt(9)).and(not(eq(5)))).select()){
            assertTrue(
                    "should be either 2, 3, 4, 5, 6, 7, 8",
                    each.equals(2) || each.equals(3) || each.equals(4) ||
                    each.equals(6) || each.equals(7) || each.equals(8)
            );
        }

    }

    @Test
    public void restrictionOperator_Where_Conjunction() throws Exception {
        final List<Integer> someNumbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        assertSame("5 should be the max", 5, max(from(someNumbers).where(conj(gt(3), lt(6))).select()));
        assertSame("4 should be the min", 4, min(from(someNumbers).where(conj(gt(3), lt(6))).select()));
    }


    @Test
    public void restrictionOperator_Where_Disjunction() throws Exception {
        final List<Integer> someNumbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        assertSame("10 should be the max", 10, max(from(someNumbers).where(disj(gt(3), lt(6))).select()));
        assertSame("1 should be the min", 1, min(from(someNumbers).where(disj(gt(3), lt(6))).select()));
    }


    @Test
    public void predicateComposition() throws Exception {
        final int offset = 3;
        final List<Integer> someNumbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        final Iterable<Integer> r = from(someNumbers).select(compose(lt(6), new Function<Integer, Integer>(){
            @Override
            public Integer apply(Integer from) throws Exception {
                return offset + from;
            }
        }));

        assertEquals("[1, 2]", r.toString());
    }



    /**
     * dummy person which will be used for testing.
     */
    private static class Person {
        private final String firtName;
        private final String lastName;

        Person(String firtName, String lastName){
            this.firtName = firtName;
            this.lastName = lastName;
        }

        public String getFirtName() {
            return firtName;
        }

        public String getLastName() {
            return lastName;
        }

        @Override
        public String toString() {
            return getLastName();
        }
    }
}
